Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dev): Wait for the router to be initialized in RemixBrowser before applying HMR changes #6767

Merged
merged 6 commits into from
Jul 11, 2023

Conversation

defjosiah
Copy link
Contributor

Fixes: #6415

Description

With the cloudflare-workers template (and likely pages), there is a race condition. Sometimes the router that is used in
the import.meta setup in the RemixBrowser component file is undefined.

My guess is that the hmr update is sent before the component is hydrated (which is where the file global
let router: Router variable is assigned).

This adds a guard to the import.meta.hot.accept function, which waits for the router to be initialized before it is used in
the rest of the function.

Testing Strategy

I discovered this in devtools, I have a reproducing example locally (same as the issue this closes).

Without my change, you'll see that the router is undefined, and the error:

browser.js:42 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'state')
    at browser.js:42:39
    at Object.emit (hmr-runtime:remix:hmr:64:9)
    at ws.onmessage (login:62:82)

is shown. There is also no HMR that works.

Screenshot 2023-07-04 at 4 07 00 PM Screenshot 2023-07-04 at 4 06 41 PM

With my change, built and applied locally, it properly does HMR and has no errors:

Screenshot 2023-07-04 at 4 04 42 PM

I looked for places to add tests for this, but I don't see any component level tests for the component, and it looked like a large can of worms to attempt to add the first tests for this component. The hmr integration test will catch if this is an issue with the existing hmr setup.

Reproduction Steps

  1. Start with the cloudflare-workers remix template
  2. Try HMR, it may or may not work, if it doesn't work, it'll show that error (or Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: http://localhost:8787/build/entry.client-EGKEKBAB.js)

@changeset-bot
Copy link

changeset-bot bot commented Jul 4, 2023

🦋 Changeset detected

Latest commit: fe8175d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@remix-run/react Patch
@remix-run/testing Patch
create-remix Patch
remix Patch
@remix-run/architect Patch
@remix-run/cloudflare Patch
@remix-run/cloudflare-pages Patch
@remix-run/cloudflare-workers Patch
@remix-run/css-bundle Patch
@remix-run/deno Patch
@remix-run/dev Patch
@remix-run/eslint-config Patch
@remix-run/express Patch
@remix-run/netlify Patch
@remix-run/node Patch
@remix-run/serve Patch
@remix-run/server-runtime Patch
@remix-run/vercel Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@remix-cla-bot
Copy link
Contributor

remix-cla-bot bot commented Jul 4, 2023

Hi @defjosiah,

Welcome, and thank you for contributing to Remix!

Before we consider your pull request, we ask that you sign our Contributor License Agreement (CLA). We require this only once.

You may review the CLA and sign it by adding your name to contributors.yml.

Once the CLA is signed, the CLA Signed label will be added to the pull request.

If you have already signed the CLA and received this response in error, or if you have any questions, please contact us at hello@remix.run.

Thanks!

- The Remix team

@remix-cla-bot
Copy link
Contributor

remix-cla-bot bot commented Jul 4, 2023

Thank you for signing the Contributor License Agreement. Let's get this merged! 🥳

@defjosiah
Copy link
Contributor Author

I'm also running with --no-restart, so there's a chance it also fixes:

#6748

@mauricevancooten
Copy link

I'm using the cloudflare-workers template and can confirm I'm getting this error too. @defjosiah I'm looking forward to your fix!

@pcattori pcattori self-assigned this Jul 5, 2023
@defjosiah defjosiah force-pushed the defjosiah/act/hmr-promise-router branch from aba7ca6 to 441848b Compare July 5, 2023 17:13
@pcattori pcattori assigned jacob-ebey and unassigned pcattori Jul 5, 2023
@jacob-ebey jacob-ebey mentioned this pull request Jul 5, 2023
@jacob-ebey
Copy link
Member

@defjosiah I have a commit over here that I'd like to see added as part of this fix: #6774

@defjosiah
Copy link
Contributor Author

@jacob-ebey: sounds good!
I can pull that in here (You can also feel free to push to my branch, but maybe the fork confuses that).

How do you want me to handle that?
I can also just accept your PR

@jacob-ebey
Copy link
Member

@jacob-ebey: sounds good! I can pull that in here (You can also feel free to push to my branch, but maybe the fork confuses that).

How do you want me to handle that? I can also just accept your PR

Feel free to pull it into your fork / this PR if you'd like to do that!

@defjosiah defjosiah force-pushed the defjosiah/act/hmr-promise-router branch from 441848b to 59552f4 Compare July 5, 2023 19:44
@defjosiah
Copy link
Contributor Author

@jacob-ebey: done! Thanks for the extra error handling

@defjosiah
Copy link
Contributor Author

Do I have to do anything else here, or will y'all handle merging it, etc.?

@jacob-ebey
Copy link
Member

jacob-ebey commented Jul 5, 2023

Do I have to do anything else here, or will y'all handle merging it, etc.?

We'll take care of it. Thanks for the contribution! 🎉

@@ -180,7 +202,7 @@ export function RemixBrowser(_props: RemixBrowserProps): ReactElement {
window.__remixContext.future.v2_normalizeFormMethod,
},
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jacob-ebey this looks like unintentional whitespace

Comment on lines +51 to +63
let hmrRouterReadyResolve: ((router: Router) => void) | undefined;
// There's a race condition with HMR where the remix:manifest is signaled before
// the router is assigned in the RemixBrowser component. This promise gates the
// HMR handler until the router is ready
let hmrRouterReadyPromise = new Promise<Router>((resolve) => {
// body of a promise is executed immediately, so this can be resolved outside
// of the promise body
hmrRouterReadyResolve = resolve;
}).catch(() => {
// This is a noop catch handler to avoid unhandled promise rejection warnings
// in the console. The promise is never rejected.
return undefined;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use a Channel here instead so you don't have to manually set resolve and check for it later to make TS happy:

Suggested change
let hmrRouterReadyResolve: ((router: Router) => void) | undefined;
// There's a race condition with HMR where the remix:manifest is signaled before
// the router is assigned in the RemixBrowser component. This promise gates the
// HMR handler until the router is ready
let hmrRouterReadyPromise = new Promise<Router>((resolve) => {
// body of a promise is executed immediately, so this can be resolved outside
// of the promise body
hmrRouterReadyResolve = resolve;
}).catch(() => {
// This is a noop catch handler to avoid unhandled promise rejection warnings
// in the console. The promise is never rejected.
return undefined;
});
let routerReady = Channel.create<Router>()
// ...
let checkRouter = await routerReady.result()
if (!checkRouter.ok) {
console.error("...")
return
}
let router = checkRouter.value
// ...
routerReady.ok(router)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine with me, however, it looks like that Channel implementation is in remix-dev package?

And it doesn't look like there's an import in this react package, so not sure how y'all would want to do that.

Also, typescript seems happy with it for me! I'm getting all the proper types out of this.

Copy link
Contributor

@pcattori pcattori Jul 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yea good point. Not worth introducing the channel abstraction just for this if its not already in this package.

For TS, I just mean that if (hmrRouterReadyResolve) { is unnecessary with channel, but needed here since TS doesn't know that hmrRouterReadyResolve is guaranteed to be set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, got it!
Yeah, I really like that Channel abstraction, probably going to shamelessly use it somewhere else 😂

@defjosiah
Copy link
Contributor Author

Hi, should I be keeping this up to date?
Wasn't sure what the merging timeline is for a contribution.

@pcattori
Copy link
Contributor

pcattori commented Jul 7, 2023

@defjosiah I'll review this with @jacob-ebey on Monday

@brophdawg11 brophdawg11 added the feat:dx Issues related to the developer experience label Jul 10, 2023
@pcattori pcattori merged commit afb5413 into remix-run:dev Jul 11, 2023
@github-actions
Copy link
Contributor

🤖 Hello there,

We just published version v0.0.0-nightly-c8936ac-20230712 which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

@github-actions
Copy link
Contributor

🤖 Hello there,

We just published version 1.19.0-pre.0 which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

@defjosiah defjosiah deleted the defjosiah/act/hmr-promise-router branch July 14, 2023 00:45
@github-actions
Copy link
Contributor

🤖 Hello there,

We just published version 1.19.0 which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed feat:dx Issues related to the developer experience package:dev
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants